from __future__ import annotations

import logging
import os
import shutil
import subprocess
from pathlib import Path
from typing import Optional
from datetime import datetime
from typing import Tuple, List, Dict
import re

LOGGER = logging.getLogger(__name__)


def ffmpeg_available() -> bool:
    """Return True if FFmpeg is available in PATH."""
    return shutil.which("ffmpeg") is not None


def ensure_ffmpeg_in_path() -> None:
    """Ensure FFmpeg exists in PATH, raise RuntimeError if missing."""
    if not ffmpeg_available():
        LOGGER.error("processing_failed: FFmpeg not found in PATH")
        raise RuntimeError("FFmpeg not found in PATH")


def extract_audio_from_video(
    video_path: str | Path,
    audio_output_path: str | Path,
    *,
    sample_rate: int = 16000,
    channels: int = 1,
    overwrite: bool = True,
) -> Path:
    """Extract WAV audio from a video file using FFmpeg.

    - Configurable sample rate and channels.
    - Returns the output path on success.
    - Raises FileNotFoundError if input video does not exist.
    - Raises RuntimeError if FFmpeg execution fails or FFmpeg is not available.

    Parameters
    ----------
    video_path : str | Path
        Input video file path.
    audio_output_path : str | Path
        Output audio file path (WAV).
    sample_rate : int, default 16000
        Target audio sample rate.
    channels : int, default 1
        Number of audio channels.
    overwrite : bool, default True
        Whether to overwrite existing output.
    """
    vp = Path(video_path)
    op = Path(audio_output_path)

    if not vp.exists():
        LOGGER.error("file_not_found: video file missing: %s", vp)
        raise FileNotFoundError(f"Video file not found: {vp}")

    # explicit PATH check for clarity and startup usage
    ensure_ffmpeg_in_path()

    ffmpeg_path: Optional[str] = shutil.which("ffmpeg")
    op.parent.mkdir(parents=True, exist_ok=True)

    cmd = [
        ffmpeg_path,
        "-y" if overwrite else "-n",
        "-i",
        str(vp),
        "-vn",
        "-ac",
        str(channels),
        "-ar",
        str(sample_rate),
        str(op),
    ]

    try:
        subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        LOGGER.info("Audio extracted: %s -> %s (sr=%d, ch=%d)", vp, op, sample_rate, channels)
    except subprocess.CalledProcessError as exc:
        LOGGER.error("processing_failed: FFmpeg error for %s: %s", vp, exc)
        raise RuntimeError(f"FFmpeg failed: {exc}")

    return op

# Helper: normalize author for matching
def _normalize_author(name: str) -> str:
    if not name:
        return "NA"
    s = name.strip()
    # remove spaces and common punctuation
    s = re.sub(r"[\s\-_/·•]+", "", s)
    return s

# Parse MP4 stem like: "YYYY-MM-DD HH.MM.SS-视频-作者-标题..."
# Returns (date_str, time_str_or_none, author_norm)
def parse_mp4_stem(stem: str) -> Tuple[str, Optional[str], Optional[str]]:
    try:
        m = re.match(r"^(20\d{2}-\d{2}-\d{2})(?:\s+(\d{2})\.(\d{2})\.(\d{2}))?\-视频\-([^\-]+)", stem)
        if not m:
            return ("", None, None)
        date_str = m.group(1)
        time_str = None
        if m.group(2) and m.group(3) and m.group(4):
            time_str = f"{m.group(2)}:{m.group(3)}:{m.group(4)}"
        author_raw = m.group(5).strip()
        author_norm = _normalize_author(author_raw)
        return (date_str, time_str, author_norm)
    except Exception:
        return ("", None, None)

# Build an index: (author_norm, date_str) -> list of entries with time and path
def index_mp4_by_author_date(root: str | Path) -> Dict[Tuple[str, str], List[Tuple[Optional[str], Path]]]:
    idx: Dict[Tuple[str, str], List[Tuple[Optional[str], Path]]] = {}
    base = Path(root)
    for p in base.rglob("*.mp4"):
        date_str, time_str, author_norm = parse_mp4_stem(p.stem)
        if not author_norm or not date_str:
            continue
        key = (author_norm, date_str)
        idx.setdefault(key, []).append((time_str, p))
    # sort entries by time (None last)
    for key, entries in idx.items():
        def _key(e):
            t = e[0]
            return datetime.strptime(t, "%H:%M:%S") if t else datetime.max
        try:
            entries.sort(key=_key)
        except Exception:
            pass
    return idx

# Find by video_id naming pattern: VID_{video_id}.mp4 or {video_id}.mp4
def find_mp4_by_video_id(video_id: str, input_root: str | Path) -> Optional[Path]:
    if not video_id or str(video_id).strip().lower() == "na":
        return None
    base = Path(input_root)
    candidates: List[Path] = []
    try:
        # exact filenames
        p1 = base / f"VID_{video_id}.mp4"
        p2 = base / f"{video_id}.mp4"
        if p1.exists():
            candidates.append(p1)
        if p2.exists():
            candidates.append(p2)
        # broader search
        if not candidates:
            for p in base.rglob("*.mp4"):
                if video_id in p.stem:
                    candidates.append(p)
        return candidates[0] if candidates else None
    except Exception:
        return None

# Find best match for given author and publish_time (YYYY-MM-DD HH:MM:SS or just date)
# Strategy:
# - Exact author_norm + date match; if publish_time has time, pick nearest; else pick first
# - If none, return None
def find_mp4_for_vm(author: str, publish_time: str, input_root: str | Path) -> Optional[Path]:
    date_part = (publish_time or "NA").split(" ")[0]
    time_part = None
    if publish_time and " " in publish_time:
        tp = publish_time.split(" ")[1]
        if tp and tp != "00:00:00":
            time_part = tp
    author_norm = _normalize_author(author or "NA")
    idx = index_mp4_by_author_date(input_root)
    candidates = idx.get((author_norm, date_part), [])
    if not candidates:
        return None
    if time_part:
        # choose nearest by absolute difference in seconds
        try:
            target = datetime.strptime(time_part, "%H:%M:%S")
            best = None
            best_diff = None
            for t, p in candidates:
                if not t:
                    continue
                cur = datetime.strptime(t, "%H:%M:%S")
                diff = abs(int((cur - target).total_seconds()))
                if best is None or diff < best_diff:
                    best = p
                    best_diff = diff
            if best is not None:
                return best
        except Exception:
            pass
    # fallback: first candidate (earliest time), or the only file
    return candidates[0][1] if candidates else None

# Combined strategy: try video_id-based match first, then author/date
def find_mp4_for_vm_or_id(author: str, publish_time: str, video_id: str, input_root: str | Path) -> Optional[Path]:
    vid_match = find_mp4_by_video_id(video_id, input_root)
    if vid_match:
        return vid_match
    return find_mp4_for_vm(author, publish_time, input_root)